#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/epoll.h>
#include <infiniband/verbs.h>
#include <rdma/rdma_cma.h>

#include "iscsi.h"
#include "iser.h"
#include "core.h"
#include "iser_rdma.h"

static LIST_HEAD(iser_portals_list);
static struct rdma_event_channel *iser_evt_channel;

struct rdma_event_channel *get_iser_evt_channel(core_t *core)
{
        if (core)
                return core->iser_evt_channel;
        else
                return iser_evt_channel;
}

int iser_rdma_create_channel(int *fd, core_t *core)
{
        int ret;

        if (core) {
                core->iser_evt_channel = NULL;
                core->iser_evt_channel = rdma_create_event_channel();
                if (unlikely(!core->iser_evt_channel)) {
                        DERROR("Failed to initialize RDMA; load kernel modules?\n");
                        EXIT(EINVAL);
                        ret = errno;
                        GOTO(err_ret, ret);
                }

                *fd = core->iser_evt_channel->fd;

        } else {
                iser_evt_channel = rdma_create_event_channel();
                if (!iser_evt_channel) {
                        DERROR("Failed to initialize RDMA; load kernel modules?\n");
                        ret = errno;
                        GOTO(err_ret, ret);
                }

                *fd = iser_evt_channel->fd;
        }

        return 0;
err_ret:
        return ret;
}

int iser_rdma_get_event(struct rdma_cm_event **ev, core_t *core)
{
        int ret;

        ret = rdma_get_cm_event(get_iser_evt_channel(core), ev);
        if (ret) {
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

int iser_rdma_ack_event(struct rdma_cm_event *ev)
{
        int ret;

        ret = rdma_ack_cm_event(ev);
        if (ret) {
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

int iser_rdma_reject(struct rdma_cm_id *cm_id)
{
        int ret;

        ret = rdma_reject(cm_id, NULL, 0);
        if (ret) {
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

int iser_rdma_create_qp(struct rdma_cm_id *cm_id, struct iser_conn *conn)
{
        int ret;
        struct ibv_qp_init_attr qp_init_attr;
        struct iser_device *dev = conn->dev;

        /* create qp */
        memset(&qp_init_attr, 0, sizeof(qp_init_attr));
        qp_init_attr.qp_context = conn;
        /* both send and recv to the same CQ */
        qp_init_attr.send_cq = dev->cq;
        qp_init_attr.recv_cq = dev->cq;
        qp_init_attr.cap.max_send_wr = MAX_WQE;
        qp_init_attr.cap.max_recv_wr = MAX_WQE;
        qp_init_attr.cap.max_send_sge = 2;      /* scatter/gather entries */
        qp_init_attr.cap.max_recv_sge = 1;
        qp_init_attr.qp_type = IBV_QPT_RC;
        /* only generate completion queue entries if requested */
        qp_init_attr.sq_sig_all = 0;

        ret = rdma_create_qp(cm_id, dev->pd, &qp_init_attr);
        if (ret ) {
                DERROR("conn:%p cm_id:%p rdma_create_qp failed, %m\n",
                        conn, cm_id);
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

int iser_rdma_destroy_qp(struct rdma_cm_id *cm_id)
{
        return ibv_destroy_qp(cm_id->qp);
}

int iser_rdma_destroy_id(struct rdma_cm_id *cm_id)
{
        return rdma_destroy_id(cm_id);
}

int iser_rdma_disconnect(struct rdma_cm_id *cm_id)
{
        return rdma_disconnect(cm_id);
}

int iser_rdma_notify(struct rdma_cm_id *cm_id)
{
        return rdma_notify(cm_id, IBV_EVENT_COMM_EST);
}

int iser_rdma_accept(struct rdma_cm_id *cm_id, struct rdma_conn_param *conn_param)
{
        return rdma_accept(cm_id, conn_param);
}

int iser_rdma_add_portal(struct addrinfo *res, short int port, core_t *core)
{
        int ret;
        int afonly = 1;
        struct sockaddr_in sock_addr;
        struct sockaddr_in6 sock_addr6;
        struct rdma_cm_id *cma_listen_id;
        struct iser_portal *portal = NULL;

        ret = ymalloc((void **)&portal, sizeof(struct iser_portal));
        if (ret) {
                DERROR("zalloc failed.\n");
                GOTO(err_ret, ret);
        }
        portal->af = res->ai_family;
        portal->port = port;

        memset(&sock_addr, 0, sizeof(sock_addr));
        memset(&sock_addr6, 0, sizeof(sock_addr6));
        if (res->ai_family == AF_INET6) {
                sock_addr6.sin6_family = AF_INET6;
                sock_addr6.sin6_port = htons(port);
                sock_addr6.sin6_addr = in6addr_any;
        } else {
                sock_addr.sin_family = AF_INET;
                sock_addr.sin_port = htons(port);
                sock_addr.sin_addr.s_addr = INADDR_ANY;
        }

        ret = rdma_create_id(get_iser_evt_channel(core), &cma_listen_id, NULL,
                             RDMA_PS_TCP);
        if (ret) {
                DERROR("rdma_create_id failed, %m\n");
                GOTO(err_ret, ret);
        }
        portal->cma_listen_id = cma_listen_id;

        rdma_set_option(cma_listen_id, RDMA_OPTION_ID,
                        RDMA_OPTION_ID_AFONLY, &afonly, sizeof(afonly));

        if (res->ai_family == AF_INET6) {
                ret = rdma_bind_addr(cma_listen_id, (struct sockaddr *) &sock_addr6);
                if (ret) {
                        DERROR("rdma_bind_addr: %s\n", strerror(ret));
                        GOTO(err_ret, ret);
                }
        } else {
                ret = rdma_bind_addr(cma_listen_id, (struct sockaddr *) &sock_addr);
                if (ret) {
                        DERROR("rdma_bind_addr: %s\n", strerror(ret));
                        GOTO(err_ret, ret);
                }
        }

        /* 0 == maximum backlog */
        ret = rdma_listen(cma_listen_id, 0);
        if (ret ) {
                DERROR("rdma_listen: %s\n", strerror(ret));
                GOTO(err_ret, ret);
        }

        list_add(&portal->iser_portal_siblings, &iser_portals_list);
        return 0;
err_ret:
        return ret;
}

void iser_rdma_delete_portals(void)
{
        int ret;
        struct iser_portal *portal, *ptmp;

        list_for_each_entry_safe(portal, ptmp, &iser_portals_list,
                                 iser_portal_siblings) {
                ret = rdma_destroy_id(portal->cma_listen_id);
                if (ret)
                        DERROR("rdma_destroy_id failed: (errno=%d %m)\n", errno);
                list_del(&portal->iser_portal_siblings);
                free(portal);
        }
}

void iser_rdma_release()
{
        core_t *core = core_self();
        struct rdma_event_channel * evt_channel = NULL;

        evt_channel = get_iser_evt_channel(core);
        if (!list_empty(&iser_portals_list)) {
                rdma_event_del(evt_channel->fd);
                iser_rdma_delete_portals();
                rdma_destroy_event_channel(evt_channel);
        }
}
